package com.xf.system.utils.smsCode;

import cn.hutool.core.util.RandomUtil;
import com.xf.system.utils.cloudApi.ali.AliApiUtils;
import com.xf.system.utils.email.EmailService;
import com.xf.system.common.model.User;
import com.xf.system.common.service.UserService;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.Collections;
import java.util.concurrent.TimeUnit;

/**
 * @author xiefengf
 * 短信/邮箱验证码服务
 */
@Service
@Slf4j
public class CodeService {

    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    @Resource
    private UserService userService;

    @Resource
    private EmailService emailService;

    @Resource
    private AliApiUtils aliApiUtils;

    /**
     * 多因子双重认证 - 发送验证码
     * @param id 用户id
     * @param multiFactorType 多因子认证方式
     * @return 是否发送成功 True-成功 False-失败
     */
    @SneakyThrows
    public Boolean sendCode(Long id,Integer multiFactorType) {
        // 1、校验Token是否有效
        // 2、从数据库获取当前用户的手机号、邮箱信息
        User userInfo = userService.getById(id);
        // 3.生成验证码
        String code = RandomUtil.randomNumbers(6);
        // 4、每个用户60s只能发一次
        Object time = redisTemplate.opsForValue().get(LoginConst.LOGIN_CODE_TIME + userInfo.getId());
        if (time != null){
            throw new RuntimeException("验证码发送过于频繁，请稍后再试！");
        }
        // 5、判断是短信验证还是邮箱验证
        if (MultiFactorEnum.SMS.getValue().equals(multiFactorType)) {
            if (StringUtils.isBlank(userInfo.getPhoneNumber())) {
                throw new RuntimeException("当前用户电话号码不存在！");
            }
            // 5.1.保存验证码到 Redis中，并设置过期时间5分钟
            redisTemplate.opsForValue().set(LoginConst.LOGIN_CODE_SMS + userInfo.getPhoneNumber(), code, LoginConst.LOGIN_CODE_TTL, TimeUnit.MINUTES);
            // 5.2. 避免验证码恶意发送
            redisTemplate.opsForValue().set(LoginConst.LOGIN_CODE_TIME + userInfo.getId(), userInfo.getId(), LoginConst.LOGIN_CODE_TIME_TTL, TimeUnit.SECONDS);
            // 5.3.阿里云发送验证码到手机号
            aliApiUtils.aliSendSms(userInfo.getPhoneNumber(), code);
            log.info(userInfo.getUserName()+"发送短信验证码成功，验证码：{}", code);
        } else if (MultiFactorEnum.EMAIL.getValue().equals(multiFactorType)) {
            if (StringUtils.isBlank(userInfo.getEmail())) {
                throw new RuntimeException("当前用户邮箱不存在！");
            }
            // 5.1.保存验证码到 Redis中，并设置过期时间5分钟
            redisTemplate.opsForValue().set(LoginConst.LOGIN_CODE_EMAIL + userInfo.getEmail(), code, LoginConst.LOGIN_CODE_TTL, TimeUnit.MINUTES);
            // 5.2. 避免验证码恶意发送
            redisTemplate.opsForValue().set(LoginConst.LOGIN_CODE_TIME + userInfo.getId(), userInfo.getId(), LoginConst.LOGIN_CODE_TIME_TTL, TimeUnit.SECONDS);
            // 5.2.163邮箱发送验证码到邮箱
            emailService.sendEmail("XX系统双因子验证","【XX系统】您的验证码为："+code+"，验证码5分钟内有效，请勿泄露。", Collections.singletonList(userInfo.getEmail()));
            log.info(userInfo.getUserName()+"发送邮箱验证码成功，验证码：{}", code);
        }
        return Boolean.TRUE;
    }

    /**
     * 多因子双重认证登录校验
     * @param id 用户id
     * @param multiFactorType 多因子认证方式
     * @param code 验证码
     * @return 是否登录成功 True-成功 False-失败
     */
    @SneakyThrows
    public Boolean multiFactorLogin(Long id,Integer multiFactorType,String code) {
        // 校验Token是否有效
        // 1.获取当前用户的手机号、邮箱信息
        User userInfo = userService.getById(id);
        // 判断是短信验证还是邮箱验证
        if (MultiFactorEnum.SMS.getValue().equals(multiFactorType)) {
            if (userInfo == null || StringUtils.isBlank(userInfo.getPhoneNumber())) {
                throw new RuntimeException("当前用户电话号码不存在！");
            }
            // 3.从Redis中获取验证码
            Object cacheCode = redisTemplate.opsForValue().get(LoginConst.LOGIN_CODE_SMS + userInfo.getPhoneNumber());
            // 4、检验验证码是否正确
            Boolean aBoolean = verifyCacheCode(code, userInfo, cacheCode);
            // 5、删除验证码
            if (aBoolean){
                redisTemplate.delete(LoginConst.LOGIN_CODE_SMS + userInfo.getPhoneNumber());
            }
            return aBoolean;
        } else if (MultiFactorEnum.EMAIL.getValue().equals(multiFactorType)) {
            if (userInfo == null || StringUtils.isBlank(userInfo.getEmail())) {
                throw new RuntimeException("当前用户邮箱不存在！");
            }
            // 3.从Redis中获取验证码
            Object cacheCode = redisTemplate.opsForValue().get(LoginConst.LOGIN_CODE_EMAIL + userInfo.getEmail());
            // 4、检验验证码是否正确
            Boolean aBoolean = verifyCacheCode(code, userInfo, cacheCode);
            // 5、删除验证码
            if (aBoolean){
                redisTemplate.delete(LoginConst.LOGIN_CODE_EMAIL + userInfo.getEmail());
            }
            return aBoolean;
        }
        // 校验失败
        return Boolean.FALSE;
    }

    /**
     * 校验验证码是否正确
     * @param code 用户输入的验证码
     * @param userInfo 用户信息
     * @param cacheCode 缓存中的验证码
     * @return 是否校验成功 True-成功 False-失败
     */
    private Boolean verifyCacheCode(String code, User userInfo, Object cacheCode) {
        if (cacheCode == null) {
            // 验证码已过期
            throw new RuntimeException("当前验证码已过期！");
        }else if (!String.valueOf(cacheCode).equals(code)){
            // 验证码输入错误
            throw new RuntimeException("当前验证码错误！");
        }else {
            // 存储Token至Redis中。结束认证流程 - 这里的Token是登录成功后的Token，取前端传递过来的Token
            redisTemplate.opsForValue().set("user"+"."+ userInfo.getUserName(),"JwtTokenUtils.getTokenString()",86400, TimeUnit.SECONDS);
            // 校验成功
            return Boolean.TRUE;
        }
    }

    /**
     * 这里是简化了的登录流程，实际项目中，登录流程会更复杂。
     * @return
     */
    @SneakyThrows
    public LoginVo login() {
        //        LoginVo loginVo = LogingService.login();
//        ConfigRespDto configRespDto = managerService.getConfig();
        // 登录，这里的登录就会包含Token的生成、存储，以及用户信息的封装。
        LoginVo loginVo = new LoginVo();
        // 查询系统配置
        ConfigRespDto configRespDto = new ConfigRespDto();
        // 如果系统配置，需要先验证账号密码、再验证手机号/邮箱，那么这里就不能直接保存Token，而是直接返回登录信息。等待多因子认证的下一步
        if (configRespDto != null && OnlineOperationStatusEnum.OPEN.getValue().equals(configRespDto.getIsMultifactor())){
            // 开启多因子认证
            loginVo.setIsNeedMultiFactor(Boolean.TRUE);
        }else {
            // 没有多因子认证，存储Token至Redis中。结束认证流程
            redisTemplate.opsForValue().set("user"+"."+ loginVo.getUserInfo().getUserName(),loginVo.getToken(),86400, TimeUnit.SECONDS);
        }
        return loginVo;
    }

    @ApiModel("登录视图")
    @Data
    public static class LoginVo {
        @ApiModelProperty("token")
        String token;
        @ApiModelProperty("刷新token")
        String refreshToken;
        @ApiModelProperty("用户信息列表")
        User userInfo;
        @ApiModelProperty("偏好胎型")
        private String preference;
        @ApiModelProperty("商户id")
        private String organizationId;
        @ApiModelProperty("商户id")
        private String parkId;
        @ApiModelProperty("是否需要强制修改密码 True-需要 False-不需要")
        private Boolean isNeedChangePassword = Boolean.FALSE;
        @ApiModelProperty("是否需要多因子二次认证 True-需要 False-不需要")
        private Boolean isNeedMultiFactor = Boolean.FALSE;
    }

    @ApiModel("配置响应")
    @Data
    public static class ConfigRespDto {
        @ApiModelProperty("多因子认证 1 打开 2 关闭")
        private Integer isMultifactor;
    }

}
